local function init_jdtls()
  local jdtls = require("jdtls")
  local root_markers = { ".git", "mvnw", "gradlew", "pom.xml", "build.gradle", ".classpath" }
  local root_dir = require("jdtls.setup").find_root(root_markers)
  local home = os.getenv("HOME")
  local project_name = vim.fn.fnamemodify(vim.fn.getcwd(), ":p:h:t")
  -- eclipse.jdt.ls stores project specific data within a folder. If you are working
  -- with multiple different projects, each project must use a dedicated data directory.
  -- This variable is used to configure eclipse to use the directory name of the
  -- current project found using the root_marker as the folder for project specific data.
  local workspace_dir = home .. "/.local/share/eclipse/" .. project_name
  local JDTLS_LOCATION = vim.fn.stdpath("data") .. "/mason/packages/jdtls"

  if root_dir == "" then
    return
  end
  if vim.fn.has("mac") == 1 then
    CONFIG = "config_mac"
  elseif vim.fn.has("unix") == 1 then
    CONFIG = "config_linux"
  else
    print("Only support mac and linux")
  end

  vim.opt_local.shiftwidth = 2
  vim.opt_local.tabstop = 2

  local config = {}

  config.settings = {
    java = {
      eclipse = {
        downloadSources = true,
      },
      configuration = {
        updateBuildConfiguration = "interactive",
        runtimes = {
          { name = "JavaSE-17", path = "/usr/lib/jvm/java-17-openjdk" },
        },
      },
      maven = {
        downloadSources = true,
      },
      implementationsCodeLens = {
        enabled = true,
      },
      referencesCodeLens = {
        enabled = false,
      },
      references = {
        includeDecompiledSources = true,
      },
      inlayHints = {
        parameterNames = {
          enabled = "all", -- literals, all, none
        },
      },
      format = {
        enabled = true,
      },
    },
    signatureHelp = { enabled = true },
    completion = {
      favoriteStaticMembers = {
        "org.hamcrest.MatcherAssert.assertThat",
        "org.hamcrest.Matchers.*",
        "org.hamcrest.CoreMatchers.*",
        "org.junit.jupiter.api.Assertions.*",
        "java.util.Objects.requireNonNull",
        "java.util.Objects.requireNonNullElse",
        "org.mockito.Mockito.*",
      },
      filteredTypes = {
        "com.sun.*",
        "io.micrometer.shaded.*",
        "java.awt.*",
        "jdk.*",
        "sun.*",
      },
    },
    contentProvider = { preferred = "fernflower" },
    sources = {
      organizeImports = {
        starThreshold = 9999,
        staticStarThreshold = 9999,
      },
    },
    codeGeneration = {
      toString = {
        template = "${object.className}{${member.name()}=${member.value}, ${otherMembers}}",
      },
      useBlocks = true,
    },
  }

  config.cmd = {
    "java", -- or '/path/to/java11_or_newer/bin/java'
    -- depends on if `java` is in your $PATH env variable and if it points to the right version.

    "-Declipse.application=org.eclipse.jdt.ls.core.id1",
    "-Dosgi.bundles.defaultStartLevel=4",
    "-Declipse.product=org.eclipse.jdt.ls.core.product",
    "-Dlog.protocol=true",
    "-Dlog.level=ALL",
    "-javaagent:" .. JDTLS_LOCATION .. "/lombok.jar",
    "-Xms1g",
    "--add-modules=ALL-SYSTEM",
    "--add-opens",
    "java.base/java.util=ALL-UNNAMED",
    "--add-opens",
    "java.base/java.lang=ALL-UNNAMED",
    "-XX:+ShowCodeDetailsInExceptionMessages",
    "-jar",
    vim.fn.glob(JDTLS_LOCATION .. "/plugins/org.eclipse.equinox.launcher_*.jar"),
    -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                       ^^^^^^^^^^^^^^
    -- Must point to the                                                     Change this to
    -- eclipse.jdt.ls installation                                           the actual version

    "-configuration",
    JDTLS_LOCATION .. "/" .. CONFIG,
    -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        ^^^^^^
    -- Must point to the                      Change to one of `linux`, `win` or `mac`
    -- eclipse.jdt.ls installation            Depending on your system.

    -- See `data directory configuration` section in the README
    "-data",
    workspace_dir,
  }

  config.root_dir = root_dir

  config.capabilities = require("tvl.util").capabilities()

  local jar_patterns = {
    "/dev/microsoft/java-debug/com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin-*.jar",
    "/dev/dgileadi/vscode-java-decompiler/server/*.jar",
    "/dev/microsoft/vscode-java-test/java-extension/com.microsoft.java.test.plugin/target/*.jar",
    "/dev/microsoft/vscode-java-test/java-extension/com.microsoft.java.test.runner/target/*.jar",
    "/dev/microsoft/vscode-java-test/java-extension/com.microsoft.java.test.runner/lib/*.jar",
    "/dev/testforstephen/vscode-pde/server/*.jar",
  }
  local plugin_path =
    "/dev/microsoft/vscode-java-test/java-extension/com.microsoft.java.test.plugin.site/target/repository/plugins/"
  local bundle_list = vim.tbl_map(function(x)
    return require("jdtls.path").join(plugin_path, x)
  end, {
    "junit-jupiter-*.jar",
    "junit-platform-*.jar",
    "junit-vintage-engine_*.jar",
    "org.opentest4j*.jar",
    "org.apiguardian.api_*.jar",
    "org.eclipse.jdt.junit4.runtime_*.jar",
    "org.eclipse.jdt.junit5.runtime_*.jar",
    "org.opentest4j_*.jar",
  })
  vim.list_extend(jar_patterns, bundle_list)
  local bundles = {}
  for _, jar_pattern in ipairs(jar_patterns) do
    for _, bundle in ipairs(vim.split(vim.fn.glob(home .. jar_pattern), "\n")) do
      if
        not vim.endswith(bundle, "com.microsoft.java.test.runner-jar-with-dependencies.jar")
        and not vim.endswith(bundle, "com.microsoft.java.test.runner.jar")
      then
        table.insert(bundles, bundle)
      end
    end
  end

  local extendedClientCapabilities = jdtls.extendedClientCapabilities
  extendedClientCapabilities.resolveAdditionalTextEditsSupport = true

  config.init_options = {
    bundles = bundles,
    extendedClientCapabilities = extendedClientCapabilities,
  }

  jdtls.start_or_attach(config)
end

return {
  {
    "nvim-treesitter/nvim-treesitter",
    opts = function(_, opts)
      if type(opts.ensure_installed) == "table" then
        vim.list_extend(opts.ensure_installed, { "java" })
      end
    end,
  },

  {
    "neovim/nvim-lspconfig",
    dependencies = { "mfussenegger/nvim-jdtls" },
    opts = {
      servers = {
        jdtls = {
          keys = {
            {
              "<leader>lo",
              function()
                require("jdtls").organize_imports()
              end,
              desc = "Organize Imports",
            },
            {
              "<leader>lR",
              function()
                require("jdtls").rename_file()
              end,
              desc = "Rename File",
            },
            {
              "<leader>lxv",
              function()
                require("jdtls").extract_variable()
              end,
              desc = "Extract Variable",
            },
            {
              "<leader>lxv",
              function()
                require("jdtls").extract_variable({ visual = true })
              end,
              mode = "v",
              desc = "Extract Variable",
            },
            {
              "<leader>lxc",
              function()
                require("jdtls").extract_constant()
              end,
              desc = "Extract Constant",
            },
            {
              "<leader>lxc",
              function()
                require("jdtls").extract_constant({ visual = true })
              end,
              mode = "v",
              desc = "Extract Constant",
            },
            {
              "<leader>lxm",
              function()
                require("jdtls").extract_method({ visual = true })
              end,
              mode = "v",
              desc = "Extract Method",
            },
          },
        },
      },
      attach_handlers = {
        jdtls = function()
          local lsp_config = require("lspconfig.server_configurations.jdtls").default_config
          local filetypes = lsp_config.filetypes
          vim.api.nvim_create_autocmd("FileType", {
            group = vim.api.nvim_create_augroup("MyJdtls", { clear = true }),
            pattern = filetypes,
            callback = function()
              init_jdtls()
            end,
          })
        end,
      },
    },
  },
  {
    "jay-babu/mason-null-ls.nvim",
    opts = function(_, opts)
      opts.ensure_installed = vim.list_extend(opts.ensure_installed, {
        "google_java_format",
      })
    end,
  },
}
